ROS 2 Launch 使用指南:参数、条件与生命周期编排
launch 文件是 ROS2 系统的启动清单:一次拉起多个节点、传参数、做条件分支、编排启动顺序、挂调试器,都写在这里。但 launch 不只是"配置文件"——它是一个用 Python 描述的、事件驱动的动作执行系统。本文基于 launch 与 launch_ros 源码(Foxy 基准,个别 CLI 选项的版本差异已标注)系统整理它的使用方法:从 Node 参数、Substitution 动态表达式,到事件处理、组件化容器与生命周期编排,适合从教程走向实际工程配置的开发者。
框架本身的两层职责划分(launch 与 launch_ros 的关系)见launch 与 launch_ros 的分层架构关系。
一、系统架构
┌─────────────────────────────────────────────┐
│ LaunchDescription │
│ (动作蓝图:声明要执行的 Action 列表) │
└─────────────────────┬───────────────────────┘
│ include_launch_description()
┌─────────────────────▼───────────────────────┐
│ LaunchService │
│ (事件循环驱动引擎,asyncio 驱动) │
│ ┌──────────────────────────────────────┐ │
│ │ LaunchContext │ │
│ │ launch_configurations(配置字典) │ │
│ │ environment variables(环境变量) │ │
│ │ event handlers(事件处理器注册表) │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘ LaunchService 内部使用 asyncio 事件循环,每个进程的启动、I/O、退出都通过异步事件驱动,不会阻塞主循环。
| 包 | 职责 |
|---|---|
launch | 平台无关核心框架(ExecuteProcess、GroupAction、TimerAction 等) |
launch_ros | ROS 2 专用扩展(Node、LifecycleNode、ComposableNodeContainer 等) |
ros2launch | ros2 launch CLI 命令实现 |
二、launch 文件结构
标准入口(被 ros2 launch 命令识别):
from launch import LaunchDescription
def generate_launch_description() -> LaunchDescription:
return LaunchDescription([
# 所有 actions 放在这里
]) 可编程入口(适合复杂逻辑,直接用 Python 运行):
import sys
from launch import LaunchService, LaunchDescription
def main():
ld = LaunchDescription()
ld.add_action(...)
ls = LaunchService(argv=sys.argv[1:])
ls.include_launch_description(ld)
return ls.run()
if __name__ == '__main__':
sys.exit(main()) generate_launch_description() 是 ros2 launch 识别的固定入口名,必须返回 LaunchDescription 对象;main() 形式需要手动管理 LaunchService,适合启动前打印调试信息的场景。
三、Node Action 详解
Node 是 ExecuteProcess 的子类,会自动将 ROS 专有参数(name、namespace、parameters、remappings)转换为 --ros-args 命令行参数。
from launch_ros.actions import Node
Node(
package='my_pkg', # ROS 包名(必填,除非 executable 是绝对路径)
executable='my_node', # 可执行文件名(必填)
name='my_node_renamed', # 覆盖节点名称(可选)
namespace='/my_ns', # 命名空间,绝对路径以 / 开头(可选)
exec_name='label_in_log', # 进程在日志中的标签,默认取 executable 的 basename
parameters=[ # 参数列表(可混合文件路径和字典)
'/path/to/params.yaml',
{'use_sim_time': True, 'max_speed': 1.5},
{'nested': {'key': 'value'}},
],
remappings=[ # 话题/服务重映射(有序,顺序影响结果)
('chatter', 'my_chatter'),
('/tf', '/robot/tf'),
],
arguments=['--ros-args', '--log-level', 'debug'], # 额外命令行参数
output='screen', # 输出方式: 'screen' | 'log' | 'both'
emulate_tty=True, # 模拟 TTY,使 Python 进程输出不被缓冲
respawn=True, # 进程异常退出后自动重启
respawn_delay=2.0, # 重启前等待秒数
prefix='gdb -ex run --args', # 进程前缀,用于 gdb/valgrind 等调试工具
cwd='/tmp', # 工作目录
env={'MY_VAR': 'value'}, # 完全替换环境变量(None 则继承当前环境)
additional_env={'MY_VAR': 'value'}, # 在当前环境基础上追加
shell=False, # 是否通过 shell 执行
log_cmd=True, # 打印展开后的完整命令(调试替换表达式用)
on_exit=[...], # 进程退出时执行的 actions
condition=IfCondition(...), # 条件执行
) 参数文件(YAML)格式:
# 精确匹配节点名称(精确名称优先级高于通配符)
/my_namespace/my_node_name:
ros__parameters:
param_a: 42
# 通配符(匹配所有节点)
/**:
ros__parameters:
use_sim_time: false 当 Node 未指定 name 时,launch 系统以通配符 /** 作为参数文件的键;节点名称完全确定时,用完整节点名作为键,可精确覆盖通配符参数。
传递参数的三种方式:
Node(
parameters=[
# 方式 1:YAML 文件路径(字符串或 Substitution)
'/abs/path/to/params.yaml',
PathJoinSubstitution([FindPackage('my_pkg'), 'config', 'params.yaml']),
# 方式 2:Python 字典(在运行时写入临时 YAML 文件)
{'use_sim_time': LaunchConfiguration('use_sim_time')},
# 方式 3:单个 Parameter 对象(通过 -p key:=value 传递)
Parameter('log_level', 'info'),
]
) 四、Substitution——动态替换表达式
Substitution 是惰性求值的占位符,在 Action 真正执行时才被解析为字符串,可以任意嵌套组合。
| Substitution | 作用 | 示例 |
|---|---|---|
LaunchConfiguration(name, default=...) | 读取 launch 参数 | LaunchConfiguration('use_sim_time', default='false') |
EnvironmentVariable(name) | 读取环境变量 | EnvironmentVariable('ROS_DOMAIN_ID') |
FindExecutable(name=...) | 查找可执行文件路径 | FindExecutable(name='python3') |
PathJoinSubstitution([...]) | 路径拼接 | 见下 |
PythonExpression([...]) | 执行 Python 表达式(沙盒,可用 math 符号) | 见下 |
ThisLaunchFileDir() / ThisLaunchFile() | 当前 launch 文件所在目录 / 完整路径 | —— |
LocalSubstitution(expr) | 访问运行时上下文局部变量 | 事件回调中读 event.reason |
FindPackage(pkg) (launch_ros) | 包的 share 目录 | FindPackage('rviz2') |
组合示例:
from launch.substitutions import PathJoinSubstitution, PythonExpression, LaunchConfiguration
from launch_ros.substitutions import FindPackage
# 路径拼接
PathJoinSubstitution([FindPackage('my_pkg'), 'config', 'my_params.yaml'])
# 逻辑与数学运算
PythonExpression(["'true' if '", LaunchConfiguration('use_gpu'), "' == 'true' else 'false'"])
PythonExpression(['1.0 / ', LaunchConfiguration('hz')])
# 多个 Substitution 放在列表中,执行时按顺序拼接
Node(
name=['robot_', LaunchConfiguration('robot_id')], # → "robot_0"
namespace=['/fleet/', LaunchConfiguration('fleet_name')],
) 五、条件执行
所有 Action 都支持 condition 参数,条件不满足时该 Action 直接跳过。
from launch.conditions import IfCondition, UnlessCondition, \
LaunchConfigurationEquals, LaunchConfigurationNotEquals
# IfCondition:参数字符串求值为 'true' 或 '1'(忽略大小写)时执行;
# 其余值(含 'yes')均视为假
Node(package='rviz2', executable='rviz2',
condition=IfCondition(LaunchConfiguration('launch_rviz')))
# UnlessCondition 相反:参数不为 'true'/'1' 时执行('false'/'0'/'no' 都会执行)
Node(package='fake_node', executable='fake',
condition=UnlessCondition(LaunchConfiguration('use_real_robot')))
# 精确等于 / 不等于
Node(condition=LaunchConfigurationEquals('backend', 'gazebo'), ...)
Node(condition=LaunchConfigurationNotEquals('backend', 'sim'), ...)
# 用 PythonExpression 组合复杂条件
Node(condition=IfCondition(PythonExpression([
"'", LaunchConfiguration('mode'), "' in ['gpu', 'turbo']"
])), ...) 注意 'yes' 不是 IfCondition 的有效真值——判断集合只有 'true' 和 '1'。
六、参数声明——DeclareLaunchArgument
必须在 LaunchDescription 顶层无条件声明,否则 ros2 launch --show-args 无法显示该参数,且可能在运行时报错。
from launch.actions import DeclareLaunchArgument
# 必填参数(无默认值,未提供时报错)
DeclareLaunchArgument('robot_name', description='机器人名称,用于命名空间前缀')
# 可选参数(有默认值,choices 限制合法值)
DeclareLaunchArgument('use_sim_time', default_value='false',
choices=['true', 'false'], description='是否使用仿真时钟')
# 默认值可以是 Substitution(运行时才求值)
DeclareLaunchArgument('config_file',
default_value=PathJoinSubstitution([
FindPackage('my_pkg'), 'config', 'default.yaml'])) 命令行传参:
ros2 launch my_pkg my_launch.py use_sim_time:=true robot_name:=robot1
ros2 launch my_pkg my_launch.py --show-args # 查看所有可用参数 七、GroupAction 与命名空间
GroupAction 默认 scoped=True,通过 Push/Pop 机制自动隔离 launch 配置和环境变量,内部修改不泄漏到外部作用域。
from launch.actions import GroupAction
from launch_ros.actions import PushRosNamespace, Node
# 给一组节点统一加命名空间(GroupAction 结束时自动弹出)
GroupAction([
PushRosNamespace('robot1'),
Node(package='my_pkg', executable='sensor'),
Node(package='my_pkg', executable='controller'),
])
# 嵌套命名空间:/fleet/robot1、/fleet/robot2
GroupAction([
PushRosNamespace('fleet'),
GroupAction([PushRosNamespace('robot1'), Node(...)]),
GroupAction([PushRosNamespace('robot2'), Node(...)]),
])
# 不隔离作用域(内部修改会影响后续 actions)
GroupAction(actions=[...], scoped=False) PushRosNamespace 本质是设置 launch_configurations['ros_namespace'],Node 执行时读取此值并与自身 namespace 参数合并。
八、嵌套 launch 文件
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource, AnyLaunchDescriptionSource
# 包含同目录 launch 文件并传参
IncludeLaunchDescription(
PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/sensors.launch.py']),
launch_arguments={'use_sim_time': LaunchConfiguration('use_sim_time')}.items()
)
# 包含其他包的 launch 文件(AnyLaunchDescriptionSource 自动识别 .py/.xml/.yaml)
IncludeLaunchDescription(
AnyLaunchDescriptionSource([FindPackage('nav2_bringup'), '/launch/navigation_launch.py']),
launch_arguments=[('map', '/maps/office.yaml')]
) 注意:launch_arguments 的键必须与被包含文件中 DeclareLaunchArgument 声明的名称一致,否则该值被设置为全局 launch 配置(不报错,但可能产生意外行为);被包含文件的必填参数未传入时,执行时抛 RuntimeError。
九、事件处理系统
事件系统是 launch 框架的核心,进程生命周期管理全部通过事件驱动实现。
可用事件处理器
| 事件处理器 | 触发时机 | 关键参数 |
|---|---|---|
OnProcessStart | 进程启动后 | target_action、on_start |
OnProcessExit | 进程退出后 | target_action、on_exit |
OnProcessIO | 进程产生 stdout/stderr 时 | target_action、on_stdout、on_stderr |
OnShutdown | 整个 launch 系统关闭时 | on_shutdown |
OnExecutionComplete | action 执行完成时 | target_action、on_completion |
OnStateTransition (launch_ros) | 生命周期节点状态转换时 | target_lifecycle_node、goal_state |
省略 target_action 时监听所有该类型的进程事件。
进程退出时执行动作
from launch.actions import RegisterEventHandler, EmitEvent, LogInfo
from launch.event_handlers import OnProcessExit, OnProcessStart
from launch.events import Shutdown
server = Node(package='my_pkg', executable='server')
client = Node(package='my_pkg', executable='client')
# 服务端启动后才启动客户端(避免竞态)
RegisterEventHandler(OnProcessStart(
target_action=server,
on_start=[LogInfo(msg='服务端已启动'), client]))
# 服务端退出时关闭整个 launch
RegisterEventHandler(OnProcessExit(
target_action=server,
on_exit=[EmitEvent(event=Shutdown(reason='server exited'))])) 捕获进程输出
事件回调可以直接返回 Action,launch 系统立即执行——适合对进程输出做模式匹配并联动控制:
def handle_stdout(event):
line = event.text.decode().strip()
if 'FATAL' in line:
return EmitEvent(event=Shutdown(reason=f'进程报错: {line}'))
RegisterEventHandler(OnProcessIO(
target_action=my_proc, on_stdout=handle_stdout, on_stderr=handle_stdout)) 进程关闭信号链
ExecuteProcess(及 Node)内置三阶段信号链:收到 Shutdown 事件后先发 SIGINT,等待 sigterm_timeout(默认 5s)超时发 SIGTERM,再等 sigkill_timeout(默认 5s)超时发 SIGKILL。调整超时:
ros2 launch my_pkg my_launch.py sigterm_timeout:=10 sigkill_timeout:=3 十、定时器与延迟执行
from launch.actions import TimerAction
# 5 秒后启动节点
TimerAction(period=5.0, actions=[Node(package='my_pkg', executable='delayed_node')])
# 延迟时间用 LaunchConfiguration 指定
TimerAction(period=LaunchConfiguration('startup_delay', default='2.0'), actions=[...])
# cancel_on_shutdown=False:收到 Shutdown 后仍执行(适合关机清理逻辑)
TimerAction(period=1.0, actions=[cleanup_action], cancel_on_shutdown=False) TimerAction 是一次性的(one-shot),触发后不会重复;基于 asyncio,精度受事件循环负载影响。
十一、组件化节点(Composable Nodes)
多个组件节点共享同一进程(component_container),消除进程间通信开销,可启用进程内零拷贝通信。
启动时一并加载:
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
ComposableNodeContainer(
name='image_pipeline_container',
namespace='/',
package='rclcpp_components',
executable='component_container',
composable_node_descriptions=[
ComposableNode(package='image_proc', plugin='image_proc::RectifyNode',
name='rectify_node', namespace='camera',
remappings=[('image', 'image_raw')]),
ComposableNode(package='image_proc', plugin='image_proc::ResizeNode',
name='resize_node', namespace='camera',
extra_arguments=[{'use_intra_process_comms': True}]), # 启用零拷贝
],
output='screen',
) 向已运行的容器动态加载:
from launch_ros.actions import LoadComposableNodes
LoadComposableNodes(
target_container='/image_pipeline_container',
composable_node_descriptions=[
ComposableNode(package='my_pkg', plugin='my_pkg::MyPlugin', name='dynamic_node'),
],
) ComposableNode 关键参数:package/plugin(必填,plugin 为完整类名如 image_proc::RectifyNode)、name/namespace/parameters/remappings(同 Node)、extra_arguments(容器专用参数如 use_intra_process_comms)。
十二、生命周期节点管理
LifecycleNode 在 Node 基础上自动订阅 /<name>/transition_event 话题并创建 /<name>/change_state 服务客户端,实现事件驱动的状态机管理。完整编排示例:
from launch import LaunchDescription
from launch.actions import RegisterEventHandler, EmitEvent, LogInfo
from launch.events import matches_action
from launch_ros.actions import LifecycleNode
from launch_ros.events.lifecycle import ChangeState
from launch_ros.event_handlers import OnStateTransition
import lifecycle_msgs.msg
def generate_launch_description():
talker = LifecycleNode(name='talker', namespace='',
package='lifecycle', executable='lifecycle_talker', output='screen')
# inactive → activate
on_inactive = RegisterEventHandler(OnStateTransition(
target_lifecycle_node=talker, goal_state='inactive',
entities=[
LogInfo(msg="talker 到达 inactive,正在激活..."),
EmitEvent(event=ChangeState(
lifecycle_node_matcher=matches_action(talker),
transition_id=lifecycle_msgs.msg.Transition.TRANSITION_ACTIVATE)),
]))
# active → 启动 listener
on_active = RegisterEventHandler(OnStateTransition(
target_lifecycle_node=talker, goal_state='active',
entities=[LifecycleNode(name='listener', namespace='',
package='lifecycle', executable='lifecycle_listener', output='screen')]))
# 触发 configure 转换
configure_event = EmitEvent(event=ChangeState(
lifecycle_node_matcher=matches_action(talker),
transition_id=lifecycle_msgs.msg.Transition.TRANSITION_CONFIGURE))
return LaunchDescription([on_inactive, on_active, talker, configure_event]) 生命周期状态机:
未配置 (Unconfigured)
│ TRANSITION_CONFIGURE
▼
非活跃 (Inactive)
│ TRANSITION_ACTIVATE ⟷ TRANSITION_DEACTIVATE
▼
活跃 (Active)
非活跃 --TRANSITION_CLEANUP--> 未配置
未配置 --TRANSITION_UNCONFIGURED_SHUTDOWN--> 终止 (Finalized) 注意事件处理器必须在节点 action 之前放入 LaunchDescription,否则转换事件发生时处理器还未注册。
十三、OpaqueFunction——完全自定义执行逻辑
OpaqueFunction 接受一个 Python 函数,执行时传入完整的 LaunchContext,可以访问所有运行时状态并动态返回 actions:
from launch.actions import OpaqueFunction
def setup_nodes(context, prefix, count):
# LaunchConfiguration 的值只能在执行期通过 perform(context) 取出
return [Node(package='my_pkg', executable='robot_controller',
namespace=f'{prefix}_{i}') for i in range(count)]
OpaqueFunction(function=setup_nodes, args=('robot', 3)) 适合"节点数量取决于运行期参数"这类无法静态声明的场景。
十四、进程 Output 控制
| 值 | 效果 |
|---|---|
'log'(默认) | stdout 到日志文件,stderr 同时到屏幕和日志 |
'screen' | stdout 和 stderr 均输出到屏幕 |
'both' | stdout 和 stderr 均输出到屏幕和日志 |
Python 进程在非 TTY 环境下默认行缓冲、输出不及时,emulate_tty=True 可解决。全局覆盖输出模式可用环境变量 OVERRIDE_LAUNCH_PROCESS_OUTPUT=screen。
十五、命令行使用
# 从包名/绝对路径启动
ros2 launch <package> <file.launch.py> [arg:=value ...]
ros2 launch /abs/path/to/file.launch.py [arg:=value ...]
# 查看支持的参数及其默认值、描述
ros2 launch my_pkg my_launch.py --show-args
# 打印 LaunchDescription 结构(不实际执行)
# 注意:--print-description 在 Humble 及之后的版本可用,Foxy 无此选项
ros2 launch my_pkg my_launch.py --print-description
# 调试模式
ros2 launch my_pkg my_launch.py --debug
# 为所有进程添加前缀(调试工具)
ros2 launch my_pkg my_launch.py 'launch-prefix:=gdb -ex run --args'
ros2 launch my_pkg my_launch.py 'launch-prefix:=valgrind --leak-check=full'
# 非交互模式(禁用 Ctrl+C 交互处理,适合 CI)
ros2 launch my_pkg my_launch.py --noninteractive 十六、完整实战示例
一个包含参数声明、条件节点、命名空间分组、子 launch 嵌套、延迟启动、进程监控的完整示例:
from launch import LaunchDescription
from launch.actions import (
DeclareLaunchArgument, GroupAction,
IncludeLaunchDescription, RegisterEventHandler,
TimerAction, EmitEvent, LogInfo,
)
from launch.conditions import IfCondition
from launch.event_handlers import OnProcessExit
from launch.events import Shutdown
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import (
LaunchConfiguration, PathJoinSubstitution, PythonExpression, ThisLaunchFileDir,
)
from launch_ros.actions import Node, PushRosNamespace, ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
from launch_ros.substitutions import FindPackage
def generate_launch_description():
# 1. 参数声明
args = [
DeclareLaunchArgument('robot_name', default_value='my_robot',
description='机器人名称,用于 ROS 命名空间'),
DeclareLaunchArgument('use_sim_time', default_value='false',
choices=['true', 'false'], description='是否使用仿真时钟'),
DeclareLaunchArgument('launch_rviz', default_value='true',
choices=['true', 'false'], description='是否启动 RViz2'),
DeclareLaunchArgument('config_dir',
default_value=PathJoinSubstitution([FindPackage('my_pkg'), 'config']),
description='配置文件目录'),
]
use_sim_time = LaunchConfiguration('use_sim_time')
config_dir = LaunchConfiguration('config_dir')
# 2. 主节点组(带命名空间隔离 + 参数文件)
controller_node = Node(
package='my_pkg', executable='controller', name='controller',
parameters=[
PathJoinSubstitution([config_dir, 'ctrl.yaml']),
{'use_sim_time': PythonExpression(["'", use_sim_time, "' == 'true'"])},
],
output='screen', respawn=True, respawn_delay=2.0)
main_group = GroupAction([
PushRosNamespace(LaunchConfiguration('robot_name')),
controller_node,
Node(package='my_pkg', executable='sensor_node', name='sensor',
remappings=[('raw_data', 'processed_data')], output='screen'),
])
# 3. 图像处理组件容器(进程内零拷贝)
image_container = ComposableNodeContainer(
name='image_container', namespace='/',
package='rclcpp_components', executable='component_container',
composable_node_descriptions=[
ComposableNode(package='image_proc', plugin='image_proc::RectifyNode',
name='rectify',
extra_arguments=[{'use_intra_process_comms': True}]),
],
output='screen')
# 4. 可选 RViz(条件执行)
rviz_node = Node(
package='rviz2', executable='rviz2', name='rviz2',
condition=IfCondition(LaunchConfiguration('launch_rviz')),
arguments=['-d', PathJoinSubstitution([config_dir, 'view.rviz'])],
output='screen')
# 5. 延迟 3 秒后包含传感器子 launch
delayed_sensors = TimerAction(period=3.0, actions=[
IncludeLaunchDescription(
PythonLaunchDescriptionSource([ThisLaunchFileDir(), '/sensors.launch.py']),
launch_arguments={'use_sim_time': use_sim_time}.items())
])
# 6. 控制器退出时关闭整个 launch(看门狗)
watchdog = RegisterEventHandler(OnProcessExit(
target_action=controller_node,
on_exit=[LogInfo(msg='控制器已退出,关闭 launch 系统...'),
EmitEvent(event=Shutdown(reason='controller exited'))]))
return LaunchDescription(args + [main_group, image_container, rviz_node,
delayed_sensors, watchdog]) 附录:全量 Action 速查表
| Action | 说明 |
|---|---|
ExecuteProcess | 启动任意系统进程 |
Node (launch_ros) | 启动 ROS 节点 |
IncludeLaunchDescription | 嵌套包含另一个 launch 文件 |
GroupAction | 将多个 action 打组,支持作用域隔离 |
DeclareLaunchArgument | 声明命令行可传入的参数 |
SetLaunchConfiguration | 运行时设置 launch 配置变量 |
SetEnvironmentVariable / UnsetEnvironmentVariable | 设置/删除环境变量 |
RegisterEventHandler / UnregisterEventHandler | 注册/注销事件监听器 |
EmitEvent | 手动触发一个事件 |
LogInfo | 打印 INFO 级别日志 |
TimerAction | 延迟指定秒数后执行一组 actions(一次性) |
OpaqueFunction | 执行任意 Python 函数,动态返回 actions |
Shutdown | 触发整个 launch 系统关闭 |
LifecycleNode (launch_ros) | 启动生命周期节点,支持状态机事件驱动 |
ComposableNodeContainer (launch_ros) | 启动 rclcpp 组件容器 |
LoadComposableNodes (launch_ros) | 向已运行容器动态加载组件节点 |
PushRosNamespace (launch_ros) | 压入 ROS 命名空间(GroupAction 结束时自动弹出) |
SetParameter / SetRemap (launch_ros) | 设置全局参数 / 话题重映射规则 |
要点
- launch 是事件驱动的动作执行系统而非静态配置:
LaunchDescription是蓝图,LaunchService用 asyncio 驱动,进程启停、输出、退出都是事件 - Substitution 惰性求值是贯穿全文的机制——想取运行期的值(参数、环境变量、上下文),一律通过 Substitution 而不是 Python 变量提前算好
- IfCondition 的真值集合只有
'true'与'1',其他任何字符串都视为假 - 事件处理器(
RegisterEventHandler)要放在触发源 action 之前注册,尤其是生命周期编排与启动顺序依赖 - 组件化容器 +
use_intra_process_comms是图像管线等大数据流场景的标准组合;launch-prefix挂 gdb/valgrind 是调试组合的标准入口
相关文章
- launch 与 launch_ros 的分层架构关系——两层仓库的职责边界与核心抽象
- ROS2 命令行工具速查:命令、接口与实例——
ros2 launch命令本身的参数与ros2命令族速查 - ROS2 零拷贝通信:loaned messages 与线程安全——组件容器零拷贝背后的 loaned message 机制